package org.light.framework.util;

import java.util.concurrent.TimeUnit;

/**
 * 计时器
 * @author de.cel
 *
 */
public class StopWatch {
	
    private long startTime;
    private long splitTime;
    private long stopTime;

    private State currentState;

    public StopWatch() {
        currentState = State.PREPARED;
    }

    public void start() {
    	
    	currentState.reset(this);
        currentState.start(this);
    }

    public void split() {
        currentState.split(this);
    }

    public void suspend() {
        currentState.suspend(this);
    }

    public void resume() {
        currentState.resume(this);
    }

    public void stop() {
        currentState.stop(this);
    }

    public void reset() {
        currentState.reset(this);
    }

    public long getElapsedMilliseconds() {
        return TimeUnit.NANOSECONDS.toMillis(getNanoTime());
    }

    public long getNanoTime() {
        return currentState.getNanoTime(this);
    }

    public long getSplitTime() {
        return TimeUnit.NANOSECONDS.toMillis(getSplitNanoTime());
    }

    public long getSplitNanoTime() {
        return currentState.getSplitNanoTime(this);
    }

    public long getTimeFromSplit() {
        return TimeUnit.NANOSECONDS.toMillis(getNanoTimeFromSplit());
    }

    public long getNanoTimeFromSplit() {
        return currentState.getNanoTimeFromSplit(this);
    }

    public boolean hasSplit() {
        return splitTime > 0;
    }

    private void checkSplitExist() {
        if (!hasSplit()) {
            throw new RuntimeException("No split exists!");
        }
    }

    private static enum State {
        PREPARED {
            @Override
            public void split(StopWatch stopWatch) {
                error(PREPARED, "split");
            }

            @Override
            public void suspend(StopWatch stopWatch) {
                error(PREPARED, "suspend");
            }

            @Override
            public void resume(StopWatch stopWatch) {
                error(PREPARED, "resume");
            }

            @Override
            public void stop(StopWatch stopWatch) {
                error(PREPARED, "stop");
            }

            @Override
            public void reset(StopWatch stopWatch) {
                // do nothing
            }

            @Override
            public long getNanoTime(StopWatch stopWatch) {
                error(PREPARED, "get time");
                return 0;
            }

            @Override
            public long getSplitNanoTime(StopWatch stopWatch) {
                error(PREPARED, "get split time");
                return 0;
            }

            @Override
            public long getNanoTimeFromSplit(StopWatch stopWatch) {
                error(PREPARED, "get time from split");
                return 0;
            }
        },
        RUNNING {
            @Override
            public void start(StopWatch stopWatch) {
                error(RUNNING, "start");
            }

            @Override
            public void resume(StopWatch stopWatch) {
                error(RUNNING, "resume");
            }

            @Override
            public long getNanoTime(StopWatch stopWatch) {
                return System.nanoTime() - stopWatch.startTime;
            }

            @Override
            public long getNanoTimeFromSplit(StopWatch stopWatch) {
                stopWatch.checkSplitExist();
                return System.nanoTime() - stopWatch.splitTime;
            }
        },
        SUSPEND {
            @Override
            public void start(StopWatch stopWatch) {
                error(SUSPEND, "start");
            }

            @Override
            public void split(StopWatch stopWatch) {
                error(SUSPEND, "split");
            }

            @Override
            public void suspend(StopWatch stopWatch) {
                error(SUSPEND, "suspend");
            }

            @Override
            public void stop(StopWatch stopWatch) {
                stopWatch.currentState = STOPPED;
            }
        },
        STOPPED {
            @Override
            public void start(StopWatch stopWatch) {
                error(STOPPED, "start");
            }

            @Override
            public void split(StopWatch stopWatch) {
                error(STOPPED, "split");
            }

            @Override
            public void suspend(StopWatch stopWatch) {
                error(STOPPED, "suspend");
            }

            @Override
            public void resume(StopWatch stopWatch) {
                error(STOPPED, "resume");
            }

            @Override
            public void stop(StopWatch stopWatch) {
                error(STOPPED, "stop");
            }
        };

        public void start(StopWatch stopWatch) {
            stopWatch.startTime = System.nanoTime();
            stopWatch.currentState = RUNNING;
        }

        public void split(StopWatch stopWatch) {
            stopWatch.splitTime = System.nanoTime();
        }

        public void suspend(StopWatch stopWatch) {
            stopWatch.stopTime = System.nanoTime();
            stopWatch.currentState = SUSPEND;
        }

        public void resume(StopWatch stopWatch) {
            long suspendedTime = System.nanoTime() - stopWatch.stopTime;
            if (stopWatch.hasSplit()) {
                stopWatch.splitTime += suspendedTime;
            }
            stopWatch.startTime += suspendedTime;
            stopWatch.currentState = RUNNING;
        }

        public void stop(StopWatch stopWatch) {
            stopWatch.stopTime = System.nanoTime();
            stopWatch.currentState = STOPPED;
        }

        public void reset(StopWatch stopWatch) {
            stopWatch.startTime = 0;
            stopWatch.splitTime = 0;
            stopWatch.stopTime = 0;
            stopWatch.currentState = PREPARED;
        }

        public long getNanoTime(StopWatch stopWatch) {
            return stopWatch.stopTime - stopWatch.startTime;
        }

        public long getSplitNanoTime(StopWatch stopWatch) {
            stopWatch.checkSplitExist();
            return stopWatch.splitTime - stopWatch.startTime;
        }

        public long getNanoTimeFromSplit(StopWatch stopWatch) {
            stopWatch.checkSplitExist();
            return stopWatch.stopTime - stopWatch.splitTime;
        }

        protected static void error(State current, String action) {
            throw new RuntimeException("Could not " + action +
                    " when stopwatch is " + current + ".");
        }
    }
}